/*
 * qv_geneticAlgorithm.c
 */

#include "qv_geneticAlgorithm.h"
#include "qv_fileIO.h"
#include "qv_geneticEnvironment.h"
#include "qv_utils.h"
#include "qv_testEnvironment.h"

static QV_botData *QV_botDataArray;

/*
 * called from game/g_main.c/LogExit() when round >= 1
 */
void qv_runGeneticAlgorithm(gclient_t *clients) {
	float *fitnessVals;
	int *parents;
	
	int roundNum = readRoundNum();
	int i;

	QV_genData *newGenData = malloc(sizeof(struct QV_genData) * getSettings()->bots);
	QV_genData *oldGenData = readGenData(roundNum);
	QV_botDataArray = malloc(sizeof(*QV_botDataArray) * getSettings()->bots);

	/* Check if testmode is on, if so go to testEnvironment */
	if (getSettings()->testmode) {
		updateTestEnvironment(clients);
		return;
	}

	/* Get game data for bots and create fitness values */
	getBotData(clients, roundNum);
	fitnessVals = getBotFitnessVals();

	/* update CSV log */
	updateBotLogFile(QV_botDataArray);

	for(i = 0; i < getSettings()->bots; i += 2) {		
		/* select/interbreed parents and write child AI-files */
		switch (getSettings()->selection_model) {
			case QV_ROULETTE_SELECTION:
				parents = selectParentBotsRoulette(fitnessVals);
				break;
			case QV_ELITE_SELECTION:
				parents = selectParentBotsElite(fitnessVals);
				break;
			default:
				parents = malloc(sizeof(int) * 2);
				parents[0] = 0;
				parents[1] = 1;
				break;
		}

		strcpy(newGenData[i].parent1, clients[parents[0]+1].pers.netname);
		strcpy(newGenData[i].parent2, clients[parents[1]+1].pers.netname);

		if (i + 1 < getSettings()->bots) {
			strcpy(newGenData[i + 1].parent1, clients[parents[0]+1].pers.netname);
			strcpy(newGenData[i + 1].parent2, clients[parents[1]+1].pers.netname);
		}
		
		interbreedBots(parents, i, roundNum, oldGenData, newGenData);
		free(parents);
	}
	free(fitnessVals);

	/* Write the data for the new children to bot files */
	writeNewBotFiles(newGenData);
	writeGenData(roundNum + 1, newGenData);
	free(newGenData);
	free(oldGenData);

	/* remove all bots of current generation */
	kickBots(clients);
	startNextRound(roundNum);

	free(QV_botDataArray);
	freeSettings();
}


/*
 * fills global QV_BOTDATA_ARRAY so we can do
 * stuff with it using our genetic algorithm
 */
void getBotData(gclient_t *clients, int roundNum) {
	int i, j, count;
	for (i = 0; i < getSettings()->bots; i++) {
		/*
		 * first client in level is always spectator so start with second
		 */
		QV_botDataArray[i].botAltName	= clients[i+1].pers.netname;
		QV_botDataArray[i].roundNum		= roundNum;
		QV_botDataArray[i].numFrags		= clients[i+1].ps.persistant[PERS_SCORE];
		QV_botDataArray[i].numDeaths	= clients[i+1].ps.persistant[PERS_KILLED];
	}

	for (i = 0; i < getSettings()->bots; i++) {
		for (j = 0, count = 0; j < getSettings()->bots; j++) {
			if(i != j) {
				if (QV_botDataArray[i].numFrags > QV_botDataArray[j].numFrags ||
					(QV_botDataArray[i].numFrags == QV_botDataArray[j].numFrags &&
					 QV_botDataArray[i].numDeaths < QV_botDataArray[j].numDeaths))
					count++;
			}
		}
		QV_botDataArray[i].rank	= count;
	}
}


/*
 * Calculates the fitness of a bot, using its frags and deaths
 */
int calcBotFitnessVal(int index, int fitnessFunc) {
	int frags = QV_botDataArray[index].numFrags;
	int deaths = QV_botDataArray[index].numDeaths;
	int ranking = QV_botDataArray[index].rank;

	if (fitnessFunc == 0)
	{
		if (frags > 0)
			return (frags * frags) - (deaths * deaths);
		return -(frags * frags) - (deaths * deaths);
	}

	if (fitnessFunc == 1)
	{
		if (frags > 0)
			return (frags * frags);
		return -(frags * frags);
	}
	
	if (fitnessFunc == 2)
	{
		return (frags * frags * frags);
	}

	if (fitnessFunc == 666)
	{
		return 666;
	}
	
	if (fitnessFunc == 3)
	{
		return (ranking * ranking);
	}

	if (fitnessFunc == 4)
	{
		int roundNum = readRoundNum();
		QV_genData *gens = readGenData(roundNum);
		
		float sumvalues = atof(gens[index].basic_gen_data[CHARACTERISTIC_ATTACK_SKILL]) - atof(gens[index].basic_gen_data[CHARACTERISTIC_REACTIONTIME]) + atof(gens[index].basic_gen_data[CHARACTERISTIC_AIM_ACCURACY]) + atof(gens[index].basic_gen_data[CHARACTERISTIC_AIM_SKILL]) + atof(gens[index].basic_gen_data[CHARACTERISTIC_ALERTNESS]);
		free(gens);
		return (sumvalues*sumvalues);	
	}

	return 1;
}


/*
 * generates (normalized & sorted & accumulated) bot fitness values
 */
float *getBotFitnessVals(void) {
	float *fitnessVals = malloc(sizeof(float) * getSettings()->bots);
	
	int i;
	for (i = 0; i < getSettings()->bots; i++) {
		int fitnessFunc = getSettings()->fitness_function;
		QV_botDataArray[i].fitness	= calcBotFitnessVal(i, fitnessFunc);
		fitnessVals[i]			= QV_botDataArray[i].fitness;
	}

	/* shift & normalize fitness vals */
	shiftFitnessVals(fitnessVals);
	normalizeFitnessVals(fitnessVals);
	return fitnessVals;
}

/*
 * shifts array of fitness values
 */
void shiftFitnessVals(float *fitnessBuffer) {
	int minFitness = (int)fitnessBuffer[0];
	int i;

	for (i = 1; i < getSettings()->bots; i++) {
		if (fitnessBuffer[i] < minFitness)
			minFitness = (int)fitnessBuffer[i];
	}

	for (i = 0; i < getSettings()->bots; i++) {
		fitnessBuffer[i] = (int)fitnessBuffer[i] - (minFitness - 1);
	}
}

/*
 * normalizes array of (shifted) fitness values
 */
void normalizeFitnessVals(float *fitnessBuffer) {
	int sum = 0;
	int i;
	
	for (i = 0; i < getSettings()->bots; i++) {
		sum += (int)fitnessBuffer[i];
	}

	for (i = 0; i < getSettings()->bots; i++) {
		fitnessBuffer[i] = fitnessBuffer[i] / ((float)sum);
	}
}


/*
 * Roulette selection, selects two parents and puts them
 * in <parents>
 */
int *selectParentBotsRoulette(float *fitnessArray) {
	int *parents = malloc(sizeof(int) * 2);
	float *percent = malloc(sizeof(float) * (getSettings()->bots + 1));

	float rand1 = randQV(0.0, 1.0);
	float rand2 = randQV(0.0, 1.0);

	int i;

	parents[0] = getSettings()->bots - 1;
	parents[1] = getSettings()->bots - 1;
	
	/* Calculate cumulative fitness percentage */
	percent[0] = 0;
	for (i = 0; i < getSettings()->bots; i++) {
		percent[i+1] = percent[i] + fitnessArray[i];
	}

	/* Choose parents, based on random numbers */
	for (i = 0; i < getSettings()->bots; i++) {
		if (rand1 >= percent[i] && rand1 < percent[i + 1])
			parents[0] = i;

		if (rand2 >= percent[i] && rand2 < percent[i + 1])
			parents[1] = i;
	}

	Com_Printf("^4\nParents selected for breeding: %d (%s), %d (%s)\n",
		parents[0], QV_botDataArray[parents[0]].botAltName, 
		parents[1], QV_botDataArray[parents[1]].botAltName);

	return parents;
}

/*
 * Elite selection, selects two parents and puts them
 * in <parents>
 */
int *selectParentBotsElite(float *fitnessArray) {
	int *parents = malloc(sizeof(int) * 2);
	int i;

	parents[0] = 0;
	parents[1] = 1;

	/* Choose the two best parents */
	for (i = 2; i < getSettings()->bots; i++) {
		if (fitnessArray[i] > fitnessArray[parents[0]] || fitnessArray[i] > fitnessArray[parents[1]]) {			
			if (fitnessArray[parents[0]] > fitnessArray[parents[1]])
				parents[1] = i;
			else
				parents[0] = i;
		}
	}

	/*Com_Printf("^4\nParents selected for breeding: %d (%s), %d (%s)\n",
		parents[0], QV_botDataArray[parents[0]].botAltName, 
		parents[1], QV_botDataArray[parents[1]].botAltName);*/

	return parents;
}

/*
 * Interbreeds two parents to create two new children.
 */
void interbreedBots(int *parents, int botIndex, int roundNum, QV_genData *oldGenData, QV_genData *newGenData) {	
	int crossoverBool = 0;

	char *childName1 = malloc(sizeof(char) * LINE_BUF_SIZE);
	char *childName2 = malloc(sizeof(char) * LINE_BUF_SIZE);

	/* Choose if cross-over will be aplied */
	if((crossoverBool = ((randQV(0.0, 1.0) <= getSettings()->crossover_rate)))) {
		/* Com_Printf("^4(QV Crossover is being used\n"); */
	}
	
	interBreedCharacts(crossoverBool, botIndex, parents, oldGenData, newGenData);
	interBreedWeights(crossoverBool, botIndex, parents, oldGenData, newGenData);

	/* Generate and store names for the new bots */
	childName1 = generateBotName(roundNum + 1, QV_CHILD_BOT_STR, (botIndex+1));
	setFixedFileGens(childName1, botIndex, newGenData);
	free(childName1);

	if (botIndex + 1 < getSettings()->bots) {
		childName2 = generateBotName(roundNum + 1, QV_CHILD_BOT_STR, (botIndex+2));	
		setFixedFileGens(childName2, botIndex + 1, newGenData);
		free(childName2);
	}
}

/*
 * Selects whether a child receives gens from either the 1st
 * or the 2nd parent, possible using crossover. There is also
 * a small change of mutation.
 */
void interBreedCharacts(int crossoverBool, int botIndex, int *parents,
		struct QV_genData *oldGens, struct QV_genData *newGens) {
	int parentIdx = 0;
	char *randVal;
	int i;
	
	if (botIndex + 1 == getSettings()->bots)
		crossoverBool = 0;

	/*
	 * iterate over bot characteristics
	 */
	for (i = 0; i < QV_NUM_BOT_CHARS; i++) {
		/* Swap point randomly determined */
		if(randQV(0.0, 1.0) <= getSettings()->swappoint_rate) {
			parentIdx = (parentIdx + 1) % 2;
			/* Com_Printf("^4(QV Swappoint\n"); */
		}
		
		/* Use parent genes, if crossoverBool == false, simply copy the parents */
		if (parentIdx == 0 || !crossoverBool) {
			/* Child 1 inherits from parent 1, Child 2 from parent 2*/
			strcpy(
				newGens[botIndex].basic_gen_data[i], 
				oldGens[parents[0]].basic_gen_data[i]);
			
			if (botIndex + 1 < getSettings()->bots)
				strcpy(
					newGens[botIndex+1].basic_gen_data[i], 
					oldGens[parents[1]].basic_gen_data[i]);
		}
		else if (parentIdx == 1) {
			/* Child 1 inherits from parent 2, Child 2 from parent 1*/
			strcpy(
				newGens[botIndex].basic_gen_data[i], 
				oldGens[parents[1]].basic_gen_data[i]);
			strcpy(
				newGens[botIndex+1].basic_gen_data[i], 
				oldGens[parents[0]].basic_gen_data[i]);
		}

		/* Possible mutation child 1 */
		if (isMutateBotCharact(i)) {
			randVal = getGaussBotCharact(i, newGens[botIndex].basic_gen_data[i]);
			strcpy(newGens[botIndex].basic_gen_data[i], randVal);
			free(randVal);
		}

		/* Possible mutation child 2*/
		if (isMutateBotCharact(i) && botIndex + 1 < getSettings()->bots) {
			randVal = getGaussBotCharact(i, newGens[botIndex + 1].basic_gen_data[i]);
			strcpy(newGens[botIndex+1].basic_gen_data[i], randVal);
			free(randVal);
		}
	}
}

/*
 * Selects whether a child receives gens from either the 1st
 * or the 2nd parent, possible using crossover. There is also
 * a small change of mutation.
 */
void interBreedWeights(int crossoverBool, int botIndex, int *parents,
		struct QV_genData *oldGens, struct QV_genData *newGens) {
	int parentIdx = 0;
	char *randVal;
	int i;

	if (botIndex + 1 == getSettings()->bots)
		crossoverBool = 0;

	/*
	 * iterate over bot characteristics
	 */
	for (i = 0; i < QV_NUM_ITEM_WEIGHTS; i++) {
		/* Swap point randomly determined */
		if(randQV(0.0, 1.0) <= getSettings()->swappoint_rate) {
			parentIdx = (parentIdx + 1) % 2;
			/* Com_Printf("^4(QV Swappoint\n"); */
		}
		
		/* Use parent genes, if crossoverBool == false, simply copy the parents */
		if (parentIdx == 0 || !crossoverBool) {
			/* Child 1 inherits from parent 1, Child 2 from parent 2*/
			strcpy(
				newGens[botIndex].item_gen_data[i], 
				oldGens[parents[0]].item_gen_data[i]);
			
			if (botIndex + 1 < getSettings()->bots)
				strcpy(
					newGens[botIndex+1].item_gen_data[i], 
					oldGens[parents[1]].item_gen_data[i]);
		}
		else if (parentIdx == 1) {
			/* Child 1 inherits from parent 2, Child 2 from parent 1*/
			strcpy(
				newGens[botIndex].item_gen_data[i], 
				oldGens[parents[1]].item_gen_data[i]);
			strcpy(
				newGens[botIndex+1].item_gen_data[i], 
				oldGens[parents[0]].item_gen_data[i]);
		}

		/* Possible mutation child 1 */		
		randVal = getGaussItemWeight(i, newGens[botIndex].item_gen_data[i]);
		strcpy(newGens[botIndex].item_gen_data[i], randVal);
		free(randVal);

		/* Possible mutation child 2*/
		if (botIndex + 1 < getSettings()->bots) {
			randVal = getGaussItemWeight(i, newGens[botIndex + 1].item_gen_data[i]);
			strcpy(newGens[botIndex+1].item_gen_data[i], randVal);
			free(randVal);
		}
	}
}


/*
 * return whether characteristic can mutate 
 */
int isMutateBotCharact(int botCharNum) {
	return (botCharNum < QV_NUM_BOT_CHARS &&
			botCharNum != CHARACTERISTIC_NAME &&
			botCharNum != CHARACTERISTIC_GENDER &&
			botCharNum != CHARACTERISTIC_WEAPONWEIGHTS &&
			botCharNum != CHARACTERISTIC_CHAT_FILE &&
			botCharNum != CHARACTERISTIC_CHAT_NAME &&
			botCharNum != CHARACTERISTIC_ITEMWEIGHTS);
}


/*
 * Returns the number of the characteristic which depends on <botCharNum>,
 * returns -1 if there is no dependency.
 */
int getMappingCharact(int botCharNum) {
	switch (botCharNum) {
		case CHARACTERISTIC_AGGRESSION:			return CHARACTERISTIC_CAMPER;
		case CHARACTERISTIC_CAMPER:				return CHARACTERISTIC_AGGRESSION;

		case CHARACTERISTIC_SELFPRESERVATION:	return CHARACTERISTIC_VENGEFULNESS;
		case CHARACTERISTIC_VENGEFULNESS:		return CHARACTERISTIC_SELFPRESERVATION;

		case CHARACTERISTIC_AIM_ACCURACY:		return CHARACTERISTIC_FIRETHROTTLE;
		case CHARACTERISTIC_FIRETHROTTLE:		return CHARACTERISTIC_AIM_ACCURACY;

		case CHARACTERISTIC_WEAPONJUMPING:		return CHARACTERISTIC_CROUCHER;
		case CHARACTERISTIC_CROUCHER:			return CHARACTERISTIC_WEAPONJUMPING;

		case CHARACTERISTIC_JUMPER:				return CHARACTERISTIC_WALKER;
		case CHARACTERISTIC_WALKER:				return CHARACTERISTIC_JUMPER;

		case CHARACTERISTIC_ATTACK_SKILL:		return CHARACTERISTIC_EASY_FRAGGER;
		case CHARACTERISTIC_EASY_FRAGGER:		return CHARACTERISTIC_ATTACK_SKILL;

		case CHARACTERISTIC_REACTIONTIME:		return CHARACTERISTIC_AIM_SKILL;
		case CHARACTERISTIC_AIM_SKILL:			return CHARACTERISTIC_REACTIONTIME;

		case CHARACTERISTIC_ALERTNESS:			return CHARACTERISTIC_VIEW_MAXCHANGE;
		case CHARACTERISTIC_VIEW_MAXCHANGE:		return CHARACTERISTIC_ALERTNESS;

		default:								return -1;
	}
}

/*
 * Normalizes basic gens according to made genetic rules
 */
char **normalizeBasicGens(QV_genData *genData, int botNum) {
	char **normalizedData = malloc(sizeof(char*) * QV_NUM_BOT_CHARS);
	int mappingCharac;
	float val1, val2, norm;
		
	int i;
	for (i = 0; i < QV_NUM_BOT_CHARS; i++) {
		normalizedData[i] = malloc(sizeof(char) * LINE_BUF_SIZE);
		
		if ((mappingCharac = getMappingCharact(i)) > -1) {
			sscanf(genData[botNum].basic_gen_data[i], "%f", &val1);
			sscanf(genData[botNum].basic_gen_data[mappingCharac], "%f", &val2);

			if (i == CHARACTERISTIC_REACTIONTIME)
				val1 = 1 - val1;

			norm = val1 / (val1 + val2);
			
			switch (i) {
				case CHARACTERISTIC_VIEW_MAXCHANGE:
					norm = norm * 359 + 1;
					break;

				case CHARACTERISTIC_VIEW_FACTOR:
					norm = norm + 0.01;	
					break;

				case CHARACTERISTIC_CHAT_CPM:	
					norm = norm * 3999 + 1;
					break;

				case CHARACTERISTIC_REACTIONTIME:
					norm = 1 - norm;
					break;

				default:
					break;
			}

			snprintf(normalizedData[i], 15, "%f", norm);
		}
		else
			strcpy(normalizedData[i], genData[botNum].basic_gen_data[i]);
	}

	return normalizedData;
}

/*
 * Normalizes item weight gens according to made genetic rules
 */
char **normalizeItemWeightGens(QV_genData *genData, int botNum) {
	char **normalizedData = malloc(sizeof(char*) * QV_NUM_ITEM_WEIGHTS);

	int i;
	for (i = 0; i < QV_NUM_ITEM_WEIGHTS; i++) {
		normalizedData[i] = malloc(sizeof(char) * LINE_BUF_SIZE);
		strcpy(normalizedData[i], genData[botNum].item_gen_data[i]);
	}

	return normalizedData;
}
